using System;
using System.IO;
using System.Diagnostics;
using Waymex.Gps.Nmea;
using System.Globalization;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Waymex.Security;


#if(!CF)
using Waymex.Diagnostics;
#endif

namespace Waymex.Gps
{
    /// <summary>
    /// This class allows acces to a connected NMEA device. The class exposes a single event
    /// that fires when a valid NMEA sentence is received from the NMEA device. 
    /// The following Sentence types are supported with specific typed objects; 
    /// BOD, GGA, GLL, GSA, GSV, RMB, RMC, WPL, PGRMC, PGRME, PGRMM, PGRMZ, PSLIB.
    /// Other sentence type are accessed via a generic sentence type.
    /// </summary>
    /// <example>C#
    /// <code><![CDATA[
    /// 
    /// // Register the event, typically in Form_Load
    /// gps.ReceiveSentence += new EventHandler<ReceiveSentenceEventArgs>(gpsDevice_ReceiveSentence);
    ///
    /// // Use a delegate as Windows Forms Controls are not thread safe
    /// private delegate void DisplayNmeaDelegate(Sentence sentence);
    ///
    /// // The Event is then raised by the assembly when data is received from the NMEA device.
    /// private void gpsDevice_ReceiveSentence(object source, ReceiveSentenceEventArgs e)
    /// {
    ///     // Call the Display function
    ///	    DisplayNmea(e.Sentence);
    /// }
    /// 
    /// private void DisplayNmea(Sentence sentence)
    /// {
    ///
    ///    // Populate a label called lblSentence
    ///
    ///    // Do NOT do this, as we may not be on the UI thread.
    ///    // lblSentence.Text = sentence.ToString();
    ///
    ///    // Check if we need to call Invoke.
    ///    if (this.InvokeRequired)
    ///    {
    ///        // Pass the this function to Invoke,
    ///        // then the call will come in on the correct
    ///        // thread and InvokeRequired will be false.
    ///        this.BeginInvoke(new DisplayNmeaDelegate(DisplayNmea), new object[] { sentence });
    ///        return;
    ///    }
    ///
    ///    //ok now as we are on the UI thread so this is OK
    ///    lblSentence.Text = sentence.ToString();
    ///
    ///    // All sentence objects are derived from Waymex.Gps.NMEA.Sentence, to get to the
    ///    // specific properties of a derived type (e.g. SentenceRmc) simply
    ///    // cast the Sentence object to the approriate type as follows;
    ///    switch (sentence.TypeId)
    ///    {
    ///        case "RMC":
    ///
    ///            // RMC Sentence received so cast to the SentenceRmc type
    ///            SentenceRmc sentenceRmc = (SentenceRmc)sentence;
    ///
    ///            // process the properties as required
    ///            lblLatitude.Text = sentenceRmc.Latitude.ToString();
    ///            lblLongitude.Text = sentenceRmc.Longitude.ToString();
    ///
    ///            break;
    ///
    ///        case "RMB":
    ///
    ///            // RMB Sentence received so cast to the SentenceRmb type
    ///           SentenceRmb sentenceRmb = (SentenceRmb)sentence;
    ///
    ///            // process the properties as required
    ///            lblDestLatitude.Text = sentenceRmb.DestinationLatitude.ToString();
    ///
    ///             break;
    ///     }
    /// }
    /// ]]></code>
    /// </example>
    public class NmeaDevice : IDisposable
    {
        private const string ERR_MSG_LIC_NONEXISTENT = "The licence for this product is invalid or missing.";
        private const string ERR_MSG_LIC_EXPIRED = "The licence for this product has expired.";
        private const double TRIAL_EXPIRY_DAYS = 30.0;

        private const string ERR_PORT_RANGE = "The value for port must be between 1 and 256 must be specified.";
        private const string ERR_BAUD_RANGE = "The value for speed must be one of the following: 1200, 4800, 9600, 19200, 57600 or 115200.";
       
        //these need moving out to a different namespace or making dynamic
        //used as the registry keys for licencing
        private const string PRODUCT_NAME = "GPS Library .Net";            
        private const string PRODUCT_VERSION = "3.2";

        //private const string INSTALLSHIELD_PRODUCT_CODE_TRIAL = "{271A284E-8913-45C0-862A-8687A329B7AC}";
        
        private LicenceStatus m_licenceStatus = LicenceStatus.licenceValid;

        private SerialLink ComPort = null;
        int m_speed = 4800;
        int m_port = 1;	//this represents com1

        private string m_strCaptureFile = "";
        private string m_strCaptureFileFilter = "";

        /// <summary>
        /// This event provides access to real time NMEA data. The Event fires when a valid
        /// NMEA Sentence is received from the NMEA device. Passed to the Event is an object
        /// representing the sentence received. The following Sentence types are supported
        /// with specific typed objects;
        /// BOD, GGA, GLL, GSA, GSV, RMB, RMC, WPL, PGRMC, PGRME, PGRMM, PGRMZ, PSLIB.
        /// Other sentence type are accessed via a generic sentence type.
        /// </summary>
        /// <example>C#
        /// <code><![CDATA[
        /// 
        /// // Register the event, typically in Form_Load
        /// gps.ReceiveSentence += new EventHandler<ReceiveSentenceEventArgs>(gpsDevice_ReceiveSentence);
        ///
        /// // Use a delegate as Windows Forms Controls are not thread safe
        /// private delegate void DisplayNmeaDelegate(Sentence sentence);
        ///
        /// // The Event is then raised by the assembly when data is received from the NMEA device.
        /// private void gpsDevice_ReceiveSentence(object source, ReceiveSentenceEventArgs e)
        /// {
        ///     // Call the Display function
        ///	    DisplayNmea(e.Sentence);
        /// }
        /// 
        /// private void DisplayNmea(Sentence sentence)
        /// {
        ///
        ///    // Populate a label called lblSentence
        ///
        ///    // Do NOT do this, as we may not be on the UI thread.
        ///    // lblSentence.Text = sentence.ToString();
        ///
        ///    // Check if we need to call Invoke.
        ///    if (this.InvokeRequired)
        ///    {
        ///        // Pass the this function to Invoke,
        ///        // then the call will come in on the correct
        ///        // thread and InvokeRequired will be false.
        ///        this.BeginInvoke(new DisplayNmeaDelegate(DisplayNmea), new object[] { sentence });
        ///        return;
        ///    }
        ///
        ///    //ok now as we are on the UI thread so this is OK
        ///    lblSentence.Text = sentence.ToString();
        ///
        ///    // All sentence objects are derived from Waymex.Gps.NMEA.Sentence, to get to the
        ///    // specific properties of a derived type (e.g. SentenceRmc) simply
        ///    // cast the Sentence object to the approriate type as follows;
        ///    switch (sentence.TypeId)
        ///    {
        ///        case "RMC":
        ///
        ///            // RMC Sentence received so cast to the SentenceRmc type
        ///            SentenceRmc sentenceRmc = (SentenceRmc)sentence;
        ///
        ///            // process the properties as required
        ///            lblLatitude.Text = sentenceRmc.Latitude.ToString();
        ///            lblLongitude.Text = sentenceRmc.Longitude.ToString();
        ///
        ///            break;
        ///
        ///        case "RMB":
        ///
        ///            // RMB Sentence received so cast to the SentenceRmb type
        ///           SentenceRmb sentenceRmb = (SentenceRmb)sentence;
        ///
        ///            // process the properties as required
        ///            lblDestLatitude.Text = sentenceRmb.DestinationLatitude.ToString();
        ///
        ///             break;
        ///     }
        /// }
        /// ]]></code>
        /// </example>
        public event EventHandler<ReceiveSentenceEventArgs> ReceiveSentence;

        //not required in .Net 2.0
        //public delegate void ReceiveSentenceEventHandler(object sender, ReceiveSentenceEventArgs e);

        //use the following code to raise the event
        //
        //		if (ReceiveSentence != null)
        //		{
        //			ReceiveSentence(this, new ReceiveSentenceEventArgs(<object>));
        //		}

        /// <summary>
        /// Constructor for the Device class.
        /// </summary>
        public NmeaDevice()
        {
            try
            {
#if(TRIAL)
                //m_licenceStatus = Waymex.Security.Licencing.GetQlmLicenceStatus(typeof(Waymex.Gps.DeviceBase), 1, "GPS Library for .Net (2.0)", 3, 0, "{69C9C619-7226-4DF0-816D-3528133BC06F}");
                //m_licenceStatus = Licencing.GetLicenceStatus(typeof(Waymex.Gps.DeviceBase));
                m_licenceStatus = Waymex.Security.Licencing.GetLicenceStatus(PRODUCT_NAME, PRODUCT_VERSION, TRIAL_EXPIRY_DAYS);

                //if there is no licence present they should not be using this version
                if (m_licenceStatus == LicenceStatus.licenceNotValid)
                {
                    throw new ApplicationException(ERR_MSG_LIC_EXPIRED);
                }
                if (m_licenceStatus == LicenceStatus.licenceNonExistent)
                {
                    throw new ApplicationException(ERR_MSG_LIC_NONEXISTENT);
                }
#else
                m_licenceStatus = LicenceStatus.licenceValid;
#endif

                ComPort = new SerialLink();
                ComPort.OnRxSentence += new SerialLink.OnRxSentenceEventHandler(ComPort_OnRxSentence);
                Waymex.Diagnostics.TraceLog.WriteHeader();

            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw ex;
            }
        }
        /// <summary>
        /// Provides a mechanism to send an NMEA sentence to the GPS Device.
        /// </summary>
        public void Send(Sentence sentence)
        {

            try
            {
                ComPort.Send(StringToByte(sentence.ToString()));
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
        }
        /// <summary>
        /// Closes the communications port and stops processing NMEA data.
        /// </summary>
        public void Stop()
        {
            try
            {
                if (ComPort.Online)
                {
                    ComPort.Close();
                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new PortException(ex.Message, ex);
            }
        }
        /// <summary>
        /// Specifies and opens the serial communication port to use.
        /// </summary>
        /// <param name="port">The serial port to use. Values in the range 1 to 256 are accepted.</param>
        public void OpenPort(int port)
        {
            OpenPort(port, 4800);
        }

        /// <summary>
        /// Specifies and opens the serial communication port to use at the specified speed.
        /// </summary>
        /// <param name="port">Specifies the serial port to use. Values in the range 1 to 256 are accepted.</param>
        /// <param name="speed">Specifies the baud rate to use.</param>
        public void OpenPort(int port, int speed)
        {
            if (port < 1 || port > 256)
                throw new ArgumentOutOfRangeException(ERR_PORT_RANGE);
            m_port = port;

            if (speed == 1200 || speed == 4800 || speed == 9600 || speed == 19200 || speed == 57600 || speed == 115200)
                m_speed = speed;
            else
                throw new ArgumentOutOfRangeException(ERR_BAUD_RANGE);
        
        }
        /// <summary>
        /// Opens the default port, COM 1 at the default speed, 4800 baud..
        /// </summary>
        public void OpenPort()
        {
            //set this to zero ensures usb will be selected
            OpenPort(1, 4800);
        }

        /// <summary>
        /// Closes the currently open port. This can safely be called at any time.
        /// </summary>
        public void ClosePort()
        {
            //do nothing, this is simply here to keep users happy.
        }

        /// <summary>
        /// Gets/Sets the communications port to use.
        /// </summary>
        [Obsolete("Property depreciated. Use the OpenPort() and ClosePort() methods to set a communication port.")]
        public Port CommunicationsPort
        {
            get
            {
                return (Port)m_port;
            }
            set
            {
                m_port = (int)value;
            }
        }

        /// <summary>
        /// Sets/Gets the Baud rate of the serial connection.
        /// </summary>
        [Obsolete("Property depreciated. Use the OpenPort() and ClosePort() methods to set a communication port.")]
        public BaudRate BaudRate
        {
            get
            {
                return (BaudRate)m_speed;
            }
            set
            {
                m_speed = (int)value;
            }
        }
        /// <summary>
        /// Opens the communications Port and starts processing the NMEA data.
        /// </summary>
        public void Start()
        {
            try
            {
                ComPort.Close();

                ComPort.Port = m_port;
                ComPort.BaudRate = m_speed;
                ComPort.Open();
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new PortException(ex.Message, ex);
            }
        }

        internal void ComPort_OnRxSentence(object source, OnRxSentenceEventArgs eargs)
        {
            string strMessage = "";
            Sentence objSentence = null;

            try
            {
                //write the line, remove the trailing CR first
                strMessage = eargs.RxSentence.ToString(CultureInfo.InvariantCulture);

                TraceLog.WriteMessage(strMessage.Substring(0, strMessage.Length - 2), System.Diagnostics.TraceLevel.Verbose, true);

                //this could return an error if garbage is received
                objSentence = new Sentence(eargs.RxSentence);

                if (objSentence != null)
                {
                    switch (objSentence.TypeId)
                    {
                        case "RMC":
                            SentenceRmc objSentenceRmc = new SentenceRmc(strMessage);
                            WriteToCaptureFile(objSentenceRmc, m_strCaptureFileFilter);
                            if (ReceiveSentence != null)
                            {
                                ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentenceRmc));
                            }
                            break;

                        case "RMB":


                                SentenceRmb objSentenceRmb = new SentenceRmb(strMessage);
                                WriteToCaptureFile(objSentenceRmb, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentenceRmb));
                                }
                            break;

                        case "BOD":

                                SentenceBod objSentenceBod = new SentenceBod(strMessage);
                                WriteToCaptureFile(objSentenceBod, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentenceBod));
                                }
                            break;
                        case "GGA":
                                SentenceGga objSentenceGga = new SentenceGga(strMessage);
                                WriteToCaptureFile(objSentenceGga, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentenceGga));
                                }
                            break;
                        case "GLL":
                                SentenceGll objSentenceGll = new SentenceGll(strMessage);
                                WriteToCaptureFile(objSentenceGll, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentenceGll));
                                }
                            break;
                        case "GSA":
                                SentenceGsa objSentenceGsa = new SentenceGsa(strMessage);
                                WriteToCaptureFile(objSentenceGsa, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentenceGsa));
                                }
                            break;
                        case "GSV":
                                SentenceGsv objSentenceGsv = new SentenceGsv(strMessage);
                                WriteToCaptureFile(objSentenceGsv, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentenceGsv));
                                }
                            break;
                        case "WPL":
                                SentenceWpl objSentenceWpl = new SentenceWpl(strMessage);
                                WriteToCaptureFile(objSentenceWpl, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentenceWpl));
                                }
                            break;
                        case "PGRMC":
                                SentencePgRmc objSentencePgRmc = new SentencePgRmc(strMessage);
                                WriteToCaptureFile(objSentencePgRmc, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentencePgRmc));
                                }
                            break;
                        case "PGRME":
                                SentencePgRme objSentencePgRme = new SentencePgRme(strMessage);
                                WriteToCaptureFile(objSentencePgRme, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentencePgRme));
                                }
                            break;
                        case "PGRMM":
                                SentencePgRmm objSentencePgRmm = new SentencePgRmm(strMessage);
                                WriteToCaptureFile(objSentencePgRmm, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentencePgRmm));
                                }
                            break;
                        case "PGRMZ":
                                SentencePgRmz objSentencePgRmz = new SentencePgRmz(strMessage);
                                WriteToCaptureFile(objSentencePgRmz, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentencePgRmz));
                                }
                            break;
                        case "PSLIB":
                                SentencePsLib objSentencePsLib = new SentencePsLib(strMessage);
                                WriteToCaptureFile(objSentencePsLib, m_strCaptureFileFilter);
                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentencePsLib));
                                }
                            break;
                        default:

                                if (ReceiveSentence != null)
                                {
                                    ReceiveSentence(this, new ReceiveSentenceEventArgs(objSentence));
                                }
                            break;

                    }
                }
            }
            catch
            {
                throw;
            }
        }
        /// <summary>
        /// Set this property to a valid filenamame and NMEA Sentences will be written to this file.
        /// </summary>
        public string CaptureFile
        {
            get
            {
                return m_strCaptureFile;
            }
            set
            {
                m_strCaptureFile = value;
            }
        }
        /// <summary>
        /// This property can be used to limit the NMEA sentences that are written to the Capture file.
        /// For example setting the property to "RMC GLL" will ensure only the RMC and GLL sentence
        /// types are written to the file. Leaving the property empty will ensure that all sentence
        /// types are written to the file.
        /// </summary>
        public string CaptureFileFilter
        {
            get
            {
                return m_strCaptureFileFilter;
            }
            set
            {
                m_strCaptureFileFilter = value;
            }
        }

        private void WriteToCaptureFile(Sentence p_objSentence, string p_strFilter)
        {
            StreamWriter swText;
            string strText = "";

            try
            {
                //make sure the capture file has been entered
                if (m_strCaptureFile.Length > 0)
                {
                    // write to file here without CR as this is on source sentence;
                    swText = new StreamWriter(m_strCaptureFile, true);

                    //see if there is a filter in operation
                    if (p_strFilter.Length > 0)
                    {
                        //see if the filter matches our TypeId
                        if (p_strFilter.IndexOf(p_objSentence.TypeId) >= 0)
                        {
                            strText = p_objSentence.ToString();
                            swText.Write(strText);
                        }
                    }
                    else
                    {
                        strText = p_objSentence.ToString();
                        swText.Write(strText);
                    }

                    swText.Close();
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        /// <summary>
        /// Closes all resources and disposes of the object. 
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
        }

        private static byte[] StringToByte(string sData)
        {
            int iIndex = 0;
            int iLength = 0;
            byte[] bytData = null;
            string strCharacter = "";
            char[] chSentence = null;

            try
            {
                iLength = sData.Length;

                chSentence = sData.ToCharArray();

                if (iLength > 0)
                {
                    bytData = new byte[iLength];

                    for (iIndex = 0; iIndex < (bytData.Length); iIndex++)
                    {
                        strCharacter = sData.Substring(iIndex, 1);
                        bytData[iIndex] = (byte)strCharacter.ToCharArray()[0];
                    }
                }
            }
            catch (Exception ex)
            {
                //TraceLog.WriteError(e)
                throw;
            }

            return bytData;

        }
    }

}
